function spec_view(varargin)
%spec_view FFT display of an incoming analog input signal.
%
%    spec_view creates an analog input object associated with the 
%    winsound adaptor with device identification 0.  The incoming 
%    signal and the fft of the incoming signal of the created analog 
%    input object are plotted.
%
%    spec_view('ADAPTORNAME', ID, CHANID) creates an analog input object
%    associated with adaptor, ADAPTORNAME, with device identification,
%    ID.  A channel is assigned to the hardware channels specified in 
%    scalar CHANID.  The incoming signal from the created analog input
%    object and the fft of the incoming signal are both plotted.
%
%    The plot is continuously updated by programming the TimerAction
%    property to plot the results from either GETDATA or PEEKDATA every 
%    0.1 seconds (GETDATA is called for the initial plot).
%
%    Examples:
%      spec_view
%      spec_view('winsound', 0, 1);
%       

%    MP 11-23-98
%    Copyright (c) 1998-1999 by The MathWorks, Inc. 
%    $Revision: 1.1 $  $Date: 1999/01/07 15:04:21 $

global data sample_rate npoints

if(isempty(sample_rate))
   sample_rate = 44100;
end;
if(isempty(npoints))
   npoints = 8192;
end;


% Error if an output argument is supplied.
if nargout > 0
   error('Too many output arguments.');
end

% Based on the number of input arguments call the appropriate 
% local function.
switch nargin
case 0
   % Create the analog input object.
   data = localInitAI;
   
   % Create the  figure.
   data = localInitFig(data);
   hFig = data.handle.figure;
case 1
   error('The ADAPTORNAME, ID and CHANID must be specified.');
case 2
   % Initialize variables.
   data = [];
   action=varargin{1};
   hFig=varargin{2};
   
   % This may fail if only the ADAPTORNAME and ID were input.  However,
   % if the ID was 0, this will work.
   try
      data=get(hFig,'UserData');
   end      
   
   % DATA will be empty if CHANID was not specified or if ID = 0.
   if isempty(data)
      error('The CHANID must be specified.');
   end
   
   % Based on the action, call the appropriate local function.
   switch action
   case 'close'
      localClose(data);
   case 'stop'
      data = localStop(data);
   case 'mouse'
      do_ptr(hFig);
   end
case 3
   % TimerAction called.
   if isa(varargin{1}, 'daqdevice')
      obj = varargin{1};
      event = varargin{2};
      localfftShowData(obj, event);
      return;
   else
      % User specified the input - adaptor, id, chanNum.
      % Create the analog input object.
      [data, errflag] = localInitAI(varargin{:});
      if errflag
         error(lasterr)
      end
      
      % Create the  figure.
      data = localInitFig(data);
      hFig = data.handle.figure;
   end
end

% Update the figure's UserData.
if ~isempty(hFig)&ishandle(hFig),
   set(hFig,'UserData',data);
end

% Update the analog input object's UserData.
if isvalid(data.ai)
   set(data.ai, 'UserData', data);
end

% ***********************************************************************   
% Create the object and get the first fft.
function [data, errflag] = localInitAI(varargin)

global sample_rate npoints
% Initialize variables.
errflag = 0;
data = [];

% Either no input arguments or all three - ADAPTORNAME, ID and CHANNELID.
switch nargin
case 0
   adaptor = 'winsound';
   id = 0;
   chan = 1;
case 3
   adaptor = varargin{1};
   id = varargin{2};
   chan = varargin{3};
otherwise
   lasterr('The ADAPTORNAME, ID and CHANID must be specified.');
   errflag = 1;
   return;
end

% Error if more than one channel was specified.
if length(chan) > 1
   lasterr('Only a single channel can be created.');
   errflag = 1;
   return
end

% Channel 2 for sound card is not allowed.
if strcmp(lower(adaptor), 'winsound') & chan == 2
   warning('Channel 1 must be used for device Winsound.');
   chan = 1;
end

% Object Configuration.
% Create an analog input object with one channel.
ai = analoginput(adaptor, id);
addchannel(ai, chan);

% Configure the analog input object.
set(ai, 'SampleRate', sample_rate);

% Configure the callback to update the display.
set(ai, 'TimerAction', {'spec_view', 'fftshowdata'});
set(ai, 'TimerPeriod', 0.1);

% Configure the analog input object to trigger manually twice.
set(ai, 'SamplesPerTrigger', npoints);
set(ai, 'TriggerRepeat', 1);
set(ai, 'TriggerType', 'manual');

% Object Execution.
% Start the analog input object.
start(ai);
trigger(ai);

% Obtain the available time and data.
[d,time] = getdata(ai, ai.SamplesPerTrigger);

% Calculate the fft.
Fs = get(ai, 'SampleRate');
blockSize = get(ai, 'SamplesPerTrigger');
[f,mag] = localDaqfft(d,Fs,blockSize);

% Create the data structure.
data.ai = ai;
data.getdata = [d time];
data.daqfft = [f mag];
data.handle = [];

% Set the object's UserData to data.
set(data.ai, 'UserData', data);

% ***********************************************************************   
% Create the display.
function data = localInitFig(data)

global sample_rate npoints

% Initialize variables.
btnColor=get(0,'DefaultUIControlBackgroundColor');

% Position the GUI in the middle of the screen
screenUnits=get(0,'Units');
set(0,'Units','pixels');
screenSize=get(0,'ScreenSize');
set(0,'Units',screenUnits);
figWidth=800;
figHeight=600;
figPos=[(screenSize(3)-figWidth)/2 (screenSize(4)-figHeight)/2 figWidth figHeight];

% Create the figure window.
hFig=figure(...                    
   'Color'             ,btnColor                 ,...
   'IntegerHandle'     ,'off'                    ,...
   'DoubleBuffer'      ,'on'                     ,...
   'DeleteFcn'         ,'spec_view(''close'',gcbf)',...
   'MenuBar'           ,'none'                   ,...
   'HandleVisibility'  ,'on'                     ,...
   'Name'              ,'Spectral Display'  ,...
   'Tag'               ,'Spec_View'  ,...
   'NumberTitle'       ,'off'                    ,...
   'Units'             ,'pixels'                 ,...
   'Position'          ,figPos                   ,...
   'UserData'          ,[]                       ,...
   'Colormap'          ,[]                       ,...
   'Pointer'           ,'arrow'                  ,...
   'WindowButtonMotionFcn', 'spec_view(''mouse'', gcbf)', ...
   'Visible'           ,'off'                     ...
   );

set(hFig, 'Units', 'Normalized');
% Create Data subplot.
hAxes(1) = axes(...
   'Position'          , [0.100 0.7 0.80 0.25],...
   'Parent'            , hFig,...
   'Tag'               , 'signal', ...
   'XLim'              , [0 get(data.ai, 'SamplesPerTrigger')],...
   'YLim'              , [-0.5 0.5], ...
   'XLimMode'          , 'manual', ...
   'YLimMode'          , 'manual' ...
   );

% Plot the data.
rate = 1/sample_rate;
aitime = [0:rate*1000:(npoints-1)*rate*1000];
hLine(1) = plot(aitime, data.getdata(:,1));
set(hAxes(1), 'XLim', [0 max(aitime)]);


% Label the plot.
xlabel('time (ms)');
ylabel('Analog Input (Volts)');
title('Analog Data Acquisition');

% Create the FFT subplot.
hAxes(2) = axes(...
   'Position'          , [0.100 0.1000 0.8 0.5],...
   'Parent'            , hFig,...
   'Tag'               , 'spectrum', ...
   'XLim'              , [0 5000], ...
   'XLimMode'			  , 'manual', ...
   'YLimMode'          , 'manual' ...
   );

% Plot the data.
hLine(2) = plot(data.daqfft(:,1),data.daqfft(:,2));
set(hAxes(2), 'XLim', [0 5000]); % max(data.daqfft(:,1))/4]);

% Label the plot.
xlabel('Frequency (Hz)');
ylabel('Magnitude (dB)');

% Create a start/stop pushbutton.
htoggle = uicontrol(...
   'Parent'          , hFig,...
   'Style'           , 'pushbutton',...
   'Units'           , 'normalized',...
   'Position'        , [0.0150 0.0111 0.1 0.0556],...
   'Value'           , 1,...
   'String'          , 'Stop',...
   'Callback'        , 'spec_view(''stop'', gcbf);');

hmenu(1) = uimenu('Parent', hFig,...
   'Label', 'File');
hmenu(2) = uimenu(hmenu(1),...
   'Label', 'Close spec_view',...
   'Callback', 'spec_view(''close'',gcbf)');
hmenu(3) = uimenu('Parent', hFig,...
   'Label', 'Help');
hmenu(4) = uimenu(hmenu(3),...
   'Label', 'Data Acquisition Toolbox',...
   'Callback', 'helpwin(''daq'')');
hmenu(5) = uimenu(hmenu(3),...
   'Label', 'spec_view',...
   'Callback', 'helpwin(''spec_view'')');

% make space for cursor readouts
hcurs = uicontrol(...
   'Parent', hFig, ...
   'Style', 'text', ...
   'Units', 'normalized', ...
   'position', [0.90 0.5 0.1 0.07], ...
   'String', 'X: 0 \nY: 0', ...
   'Tag', 'Spec_Info');

hcurs = uicontrol(...
   'Parent', hFig, ...
   'Style', 'text', ...
   'Units', 'normalized', ...
   'position', [0.90 0.9 0.1 0.07], ...
   'String', 'X: 0 \nY: 0', ...
   'Tag', 'Signal_Info');

% Store the handles in the data matrix.
data.handle.figure = hFig;
data.handle.axes = hAxes;
data.handle.line = hLine;
data.handle.toggle = htoggle;
data.state = 0;

% Set the axes handlevisibility to off.
set(hAxes, 'HandleVisibility', 'on');

% Store the data matrix and display figure.
set(hFig,'Visible','on','UserData',data,'HandleVisibility', 'on');

return

% ***********************************************************************  
% Close the figure window.
function localClose(data)

% Stop the device if it is running and delete the object.
if isvalid(data.ai)
   if strcmp(get(data.ai, 'Running'), 'On')
      stop(data.ai);
   end
   delete(data.ai);
end

% Close the figure window.
delete(data.handle.figure);

% ***********************************************************************  
% Stop or start the device.
function data = localStop(data)

% Based on the state either stop or start.
if data.state == 0
   % Stop the device.
   stop(data.ai);
   set(data.handle.toggle, 'String', 'Start');
   
   % Store the new state.
   data.state = 1;
else
   % Start the device.
   start(data.ai);
   set(data.handle.toggle, 'String', 'Stop');
   
   % Store the new state.
   data.state = 0;
end

% ***********************************************************************  
% Calculate the fft of the data.
function [f, mag] = localDaqfft(data,Fs,blockSize)

% Calculate the fft of the data.
xFFT = fft(data);
xfft = abs(xFFT);

% Avoid taking the log of 0.
index = find(xfft == 0);
xfft(index) = 1e-17;

mag = 20*log10(xfft);
mag = mag(1:blockSize/2);

f = (0:length(mag)-1)*Fs/blockSize;
f = f(:);

% ***********************************************************************  
% Update the plot.
function localfftShowData(obj,event)

% Get the handles.
data = obj.UserData;

hFig = data.handle.figure;
hAxes = data.handle.axes;
hLine = data.handle.line;

% Execute a peekdata.
x = peekdata(obj, obj.SamplesPerTrigger);

% FFT calculation.
Fs = obj.SampleRate;
blockSize = obj.SamplesPerTrigger;
[f,mag] = localDaqfft(x,Fs,blockSize);

% Dynamically modify Analog axis as we go.
maxX=max(x);
minX=min(x);
yax1=get(hAxes(1),'YLim');
if minX<yax1(1),
   yax1(1)=minX;
end
if maxX>yax1(2),
   yax1(2)=maxX;
end
set(hAxes(1),'YLim',yax1)

% Dynamically modify Frequency axis as we go.
maxF=max(f);
minF=min(f);
imax = find(f > 3500);
xax=get(hAxes(2),'XLim');
if minF<xax(1),
   xax(1)=minF;
end
if maxF>xax(2),
   xax(2)=maxF;
end
xax=[0 3500];
set(hAxes(2),'XLim',xax)

% Dynamically modify Magnitude axis as we go.
maxM=max(mag);
minM=min(mag);
yax2=get(hAxes(2),'YLim');
if minM<yax2(1),
   yax2(1)=minM;
end
if maxM>yax2(2),
   yax2(2)=maxM;
end
yax2(1) = -35;
set(hAxes(2),'YLim',yax2)
set(hAxes(2), 'Tag', 'spectrum');
set(hAxes(1), 'Tag', 'signal');

% Update the plots.
set(hLine(1), 'YData', x(:,1));
set(hLine(2), 'XData', f([1:imax(1)],1), 'YData', mag([1:imax(1)],1));
drawnow;
return


function [axis_ptr, axis_flag] = localPtrpos(h_fig, h_axes, ptr_type);

% PTRPOS - obtains the pointer position relative to the axes
%
%    [PTR, FLAG] = PTRPOS(H_FIG, H_AXES) returns the coordinates
%    of the current pointer position within the figure window H_FIG
%    relative to the axes specified by the object handle H_AXES.  The
%    (X, Y) pointer coordinates are returned as the row vector PTR,
%    and FLAG is a logical value to indicate whether or not the
%    pointer is within axes limits.
%
%    [...] = PTRPOS(H_FIG, H_AXES, 'image') returns coordinates
%    suitable for image data.  The coordinates are rounded to
%    the nearest integer, and the value of FLAG is calculated
%    to prevent pointer values exceeding image indices.

% By:   S.C. Molitor (smolitor@bme.jhu.edu)
% Date: May 17, 1999

% check input arguments

if ((nargin < 2) | (nargin > 3))
   return
elseif (~istype(h_fig, 'figure'))
   return
elseif (~istype(h_axes, 'axes'))
   return
elseif (nargin == 2)
   ptr_type = '';
elseif (~ischar(ptr_type))
   return
end

% determine pointer position normalized to the screen size

scr_size = get(0, 'ScreenSize');
ptr_pos = get(0, 'PointerLocation');
ptr_x = (ptr_pos(1) - scr_size(1))/scr_size(3);
ptr_y = (ptr_pos(2) - scr_size(2))/scr_size(4);

% determine the axis position normalized to the screen size
scr_size = [0 0 1 1]; 
figure_pos = get(h_fig, 'Position');
axis_pos = get(h_axes, 'Position');
axis_x1 = ((figure_pos(1) + axis_pos(1)*figure_pos(3)) - scr_size(1))/scr_size(3);
axis_x2 = ((axis_x1 + axis_pos(3)*figure_pos(3)) - scr_size(1))/scr_size(3);
axis_y1 = ((figure_pos(2) + axis_pos(2)*figure_pos(4)) - scr_size(2))/scr_size(4);
axis_y2 = ((axis_y1 + axis_pos(4)*figure_pos(4)) - scr_size(2))/scr_size(4);

% normalize pointer X coordinate relative to X axis limits
% account for reversed X axis direction

x_lim = get(h_axes, 'XLim');
if (strcmp(get(h_axes, 'XDir'), 'reverse'))
   axis_ptr(1) = x_lim(2) + (x_lim(1) - x_lim(2))*(ptr_x - axis_x1)/(axis_x2 - axis_x1);
else
   axis_ptr(1) = x_lim(1) + (x_lim(2) - x_lim(1))*(ptr_x - axis_x1)/(axis_x2 - axis_x1);
end

% normalize pointer Y coordinate relative to Y axis limits
% account for reversed Y axis direction

y_lim = get(h_axes, 'YLim');
if (strcmp(get(h_axes, 'YDir'), 'reverse'))
   axis_ptr(2) = y_lim(2) + (y_lim(1) - y_lim(2))*(ptr_y - axis_y1)/(axis_y2 - axis_y1);
else
   axis_ptr(2) = y_lim(1) + (y_lim(2) - y_lim(1))*(ptr_y - axis_y1)/(axis_y2 - axis_y1);
end

% determine whether pointer falls within axis range
% round to nearest integer to prevent out of range pixel indices

if (strcmp(ptr_type, 'image'))
   axis_ptr = round(axis_ptr);
   if ((axis_ptr(1) >= ceil(x_lim(1))) & (axis_ptr(1) <= floor(x_lim(2))) & ...
         (axis_ptr(2) >= ceil(y_lim(1))) & (axis_ptr(2) <= floor(y_lim(2))))
      axis_flag = 1;
   else
      axis_flag = 0;
   end
else
   if ((axis_ptr(1) >= x_lim(1)) & (axis_ptr(1) <= x_lim(2)) & ...
         (axis_ptr(2) >= y_lim(1)) & (axis_ptr(2) <= y_lim(2)))
      axis_flag = 1;
   else
      axis_flag = 0;
   end
end
return

function [] = do_ptr(h_fig);

% doptr - returns the pixel info at the pointer location 
%
%    doptr(H_FIG) serves as the ButtonMotion callback for the figure
%    H_FIG, displaying the data measurement at the current location of
%    the pointer.

% By:   S.C. Molitor (smolitor@bme.jhu.edu)
% Date: May 17, 1999
% modified 7/9/2001 P. Manis.
% check input arguments

if (nargin ~= 1)
   return
elseif (~istype(h_fig, 'figure'))
   disp('Not figure')
   return
end

if(~strcmp(get(h_fig, 'Tag'), 'Spec_View')) % not our window...
   disp('Not Spec_View')
   return;
end;
% do nothing if no text object for display
% do nothing if pointer is not in figure window

h_text = findobj(h_fig, 'Type', 'uicontrol', 'Tag', 'Spec_Info');
if (length(h_text) ~= 1)
   disp('Spec_Info not uicontrol.....')
   return
elseif (get(0, 'PointerWindow') ~= h_fig)
   disp('PointerWindow not matched')
   return
end

h_text2 = findobj(h_fig, 'Type', 'uicontrol', 'Tag', 'Signal_Info');
if (length(h_text2) ~= 1)
   disp('Spec_Info not uicontrol.....')
   return
elseif (get(0, 'PointerWindow') ~= h_fig)
   disp('PointerWindow not matched')
   return
end
% update text if pointer is over image axes

h_axes = findobj(h_fig, 'Type', 'axes', 'Tag', 'spectrum');
if (length(h_axes) == 1)
   [ptr_pos, ptr_flag1] = localPtrpos(h_fig, h_axes);
   if (ptr_flag1)
      set(h_fig, 'Pointer', 'crosshair');
      ptr_text = sprintf('%7.1f Hz  %7.1f db', ptr_pos(1), ptr_pos(2));
      set(h_text, 'String', ptr_text);
     end;
   
end
h_axes = findobj(h_fig, 'Type', 'axes', 'Tag', 'signal');
if (length(h_axes) == 1)
   [ptr_pos, ptr_flag2] = localPtrpos(h_fig, h_axes);
   if (ptr_flag2)
      set(h_fig, 'Pointer', 'crosshair');
      ptr_text = sprintf('%7.1f ms  %7.3f mV', ptr_pos(1), ptr_pos(2));
      set(h_text2, 'String', ptr_text);
   end;
end
if(~ptr_flag2 & ~ptr_flag1)
      set(h_fig, 'Pointer', 'arrow');
   end;
   return

% update text if pointer is over image axes

h_axes = findobj(h_fig, 'Type', 'axes', 'Tag', 'image');
if (length(h_axes) == 1)
   [ptr_pos, ptr_flag] = localPtrpos(h_fig, h_axes);
   if ((ptr_pos(1) >= 1) & (ptr_pos(1) <= size(AIF_IMAGE.data, 2)) & ...
         (ptr_pos(2) >= 1) & (ptr_pos(2) <= size(AIF_IMAGE.data, 1)) & ptr_flag)
      %      ptr_val = double(AIF_IMAGE.data(ptr_pos(2), ptr_pos(1)));
      ptr_text = sprintf('Intensity at (%d, %d) = %d', ptr_pos(1), ptr_pos(2), ptr_val);
      set(h_text, 'String', ptr_text);
   end
end
return
